/*
 * main.cpp
 *
 *  Created on: 01.04.2013
 *      Author: SomeChineseGuy
 */

#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#define GL_GLEXT_PROTOTYPES
#define GLFW_INCLUDE_GL3
#include <GL/glew.h>
#include <GL/glfw.h>

using namespace std;

vector<float> vertices;
int colorSize;
GLuint buffer;
GLuint vao;
GLuint shaderProgram;
GLuint offsetLocation, frustumScale, zNear, zFar;
glm::vec2 shiftCoord(0.0f, 0.0f);
float loopTime;

GLuint positionLoc;
GLuint projMatrixLoc;
GLuint viewMatrixLoc;
GLuint modelMatrixLoc;
GLuint mvpMatrixLoc;

glm::mat4 modelMatrix;
glm::mat4 viewMatrix;
glm::mat4 projectionMatrix;
glm::mat4 MVPMatrix;
float projMatrix[16];

vector<float> readMeshToVector(const char *filename) {
	ifstream file;
	file.open(filename, ios::in);

	vector<float> mesh;

	unsigned int verticesNum, pointsNum;
	file >> verticesNum >> pointsNum;

	for (unsigned int i = 0; i < verticesNum; i++) {
		for (unsigned int j = 0; j < pointsNum; j++) {
			float cur;
			file >> cur;
			mesh.push_back(cur);
		}
	}

	file.close();

	return mesh;
}

string readShadertoString(const char* filename) {

	ifstream file;
	file.open(filename, ios::in);

	string shaderStr;
	shaderStr.assign(istreambuf_iterator<char>(file),
			istreambuf_iterator<char>());

	file.close();

	return shaderStr;

}

GLuint createShader(GLenum type, string sourceCode, string fName) {

	GLuint shaderHandle = glCreateShaderObjectARB(type);

	const char* charData = sourceCode.c_str();
	glShaderSourceARB(shaderHandle, 1, &charData, NULL);

	glCompileShaderARB(shaderHandle);
	GLint status;
	glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &status);
	if (status == GL_FALSE) {
		GLint len;
		glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &len);
		char* logMessage = new char[len + 1];
		glGetShaderInfoLog(shaderHandle, len, NULL, logMessage);
		cout << "Shader compilation error:" << endl;
		cout << "\t[" << fName << "]: " << logMessage << endl;
		delete[] logMessage;
	}

	return shaderHandle;

}

GLuint createShaderProgram(vector<GLuint> shaders) {

	GLuint program = glCreateProgram();
	for (unsigned short i = 0; i < shaders.size(); i++) {
		glAttachShader(program, shaders[i]);
	}

	glLinkProgram(program);
	GLint status;
	glGetProgramiv(program, GL_COMPILE_STATUS, &status);
	if (status == GL_FALSE) {
		GLint len;
		glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
		char* logMessage = new char[len + 1];
		glGetProgramInfoLog(program, len, NULL, logMessage);
		cout << "Shader link error:" << endl;
		cout << logMessage << endl;
		delete[] logMessage;
	}

	return program;

}

void drawObjectsinit() {

	projectionMatrix = glm::perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
	viewMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
	modelMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
	//modelMatrix = glm::rotate(modelMatrix, 45.0f, glm::vec3(0.0f, 1.0f, 0.0f));
	modelMatrix = glm::translate(modelMatrix, glm::vec3(0.0f, 0.0f, -10.0f));
	MVPMatrix = projectionMatrix * viewMatrix * modelMatrix;


	glGenVertexArrays(1, &vao);

	vector<GLuint> shaders;

	shaders.push_back(
			createShader(GL_VERTEX_SHADER,
					readShadertoString("data/shaders/perspective-matrix.vert"),
					"data/shaders/perspective-matrix.vert"));
	shaders.push_back(
			createShader(GL_FRAGMENT_SHADER,
					readShadertoString("data/shaders/simple-color.frag"),
					"data/shaders/simple-color.frag"));

	shaderProgram = createShaderProgram(shaders);

	positionLoc = glGetAttribLocation(shaderProgram, "position");
	//projMatrixLoc = glGetUniformLocation(shaderProgram, "projectionMatrix");
	//viewMatrixLoc = glGetUniformLocation(shaderProgram, "viewMatrix");
	//modelMatrixLoc = glGetUniformLocation(shaderProgram, "modelMatrix");
	mvpMatrixLoc = glGetUniformLocation(shaderProgram, "MVPMatrix");


	vertices = readMeshToVector("data/mesh/triangles-build.vrt");

	glGenBuffers(1, &buffer);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), &vertices[0],
			GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	//frustumScale = glGetUniformLocation(shaderProgram, "frustumScale");
	//zNear = glGetUniformLocation(shaderProgram, "zNear");
	//zFar = glGetUniformLocation(shaderProgram, "zFar");

	glBindVertexArray(vao);
	glEnableVertexAttribArray(positionLoc);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glBindVertexArray(0);

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CCW);

	//glEnable(GL_DEPTH_TEST);
	//glDepthFunc(GL_LESS);

}

void moveVert(float &x, float &y) {

	const float loopLen = 1.0f;
	float elapsedTime = glfwGetTime() / 1000;
	float currentLoopTime = fmodf(elapsedTime, loopLen);
	x = currentLoopTime * 0.1;
	y = currentLoopTime * 0.1;

}

float calculateTime() {
	const float loopLen = 1.0f;
	float elapsedTime = glfwGetTime() / 1000;
	return fmodf(elapsedTime, loopLen);
}

void transform() {

	//moveVert(shiftCoord.x, shiftCoord.y);
	loopTime = calculateTime();

}

void draw() {

	glClear(GL_COLOR_BUFFER_BIT);

	glUseProgram(shaderProgram);
	glBindVertexArray(vao);
	//glUniform1f(frustumScale, 1.0f);
	//glUniform1f(zNear, 1.0f);
	//glUniform1f(zFar, 3.0f);
	//glUniformMatrix4fv(modelMatrixLoc, 1, GL_FALSE, glm::value_ptr(modelMatrix));
	//glUniformMatrix4fv(viewMatrixLoc, 1, GL_FALSE, glm::value_ptr(viewMatrix));
	//glUniformMatrix4fv(projMatrixLoc, 1, GL_FALSE, glm::value_ptr(projectionMatrix));
	//glUniformMatrix4fv(projMatrixLoc, 1, GL_FALSE, projMatrix);
	glUniformMatrix4fv(mvpMatrixLoc, 1, GL_FALSE, glm::value_ptr(MVPMatrix));
	glDrawArrays(GL_TRIANGLES, 0, 36);

	glfwSwapBuffers();
}

int main(void) {

	cout << "Started" << endl;

	//Initializing GLFW framework
	GLuint glfwInitResult = glfwInit();
	if (glfwInitResult != GL_TRUE) {
		cout << "GLFW Init Error" << endl;
		glfwTerminate();
		return 0;
	}

	//Window parameters for GLFW
	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4); // 4x antialiasing
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3); // We want OpenGL 3.3
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //We don't want the old OpenGL
	glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

	GLuint openResult = glfwOpenWindow(1024, 768, 0, 0, 0, 0, 24, 0, GLFW_WINDOW);
	glfwSetWindowTitle("nyaa");
	if (openResult == GL_FALSE) {
		cout << ": GLFW open window error" << endl;
		glfwTerminate();
		return 1;
	}

	//Initializing GLEW framework
	glewExperimental = true;
	GLenum glewInitResult = glewInit();
	if (glewInitResult != GLEW_OK) {
		cout << "GLEW Init Error" << endl;
		glfwTerminate();
		return 0;
	}

	drawObjectsinit();

	int escPressed = GLFW_RELEASE;
	bool isRunning = true;
	while (isRunning) {

		escPressed = glfwGetKey(GLFW_KEY_ESC);
		isRunning = ((escPressed == GLFW_RELEASE)
				&& (glfwGetWindowParam(GLFW_OPENED)));

		transform();
		draw();

	}

	cout << "Finished" << endl;

	return 0;

}
